//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//					        FAT16/32 File IO Library
//								    V2.6
// 	  						  Ultra-Embedded.com
//						    Copyright 2003 - 2011
//
//   					  Email: admin@ultra-embedded.com
//
//								License: GPL
//   If you would like a version with a more permissive license for use in
//   closed source commercial applications please contact me for details.
//-----------------------------------------------------------------------------
//
// This file is part of FAT File IO Library.
//
// FAT File IO Library is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// FAT File IO Library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with FAT File IO Library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "fat_defs.h"
#include "fat_access.h"
#include "fat_table.h"
#include "fat_write.h"
#include "fat_misc.h"
#include "fat_string.h"
#include "fat_filelib.h"
#include "fat_cache.h"

//-----------------------------------------------------------------------------
// Locals
//-----------------------------------------------------------------------------
static FL_FILE			_files[FATFS_MAX_OPEN_FILES];
static int				_filelib_init = 0;
static int				_filelib_valid = 0;
static struct fatfs		_fs;
static struct fat_list	_open_file_list;
static struct fat_list	_free_file_list;

//-----------------------------------------------------------------------------
// Macros
//-----------------------------------------------------------------------------

// Macro for checking if file lib is initialised
#define CHECK_FL_INIT()		{ if (_filelib_init==0) fl_init(); }

#define FL_LOCK(a)			do { if ((a)->fl_lock) (a)->fl_lock(); } while (0)
#define FL_UNLOCK(a)		do { if ((a)->fl_unlock) (a)->fl_unlock(); } while (0)

//-----------------------------------------------------------------------------
// Local Functions
//-----------------------------------------------------------------------------
//static void				_fl_init();


//-----------------------------------------------------------------------------
// _allocate_file: Find a slot in the open files buffer for a new file
//-----------------------------------------------------------------------------
static FL_FILE* _allocate_file(void)
{
	// Allocate free file
	struct fat_node *node = fat_list_pop_head(&_free_file_list);

	// Add to open list
	if (node)
		fat_list_insert_last(&_open_file_list, node);

	return fat_list_entry(node, FL_FILE, list_node);
}
//-----------------------------------------------------------------------------
// _check_file_open: Returns true if the file is already open
//-----------------------------------------------------------------------------
static int _check_file_open(FL_FILE* file)
{
	struct fat_node *node;

	// Compare open files
	fat_list_for_each(&_open_file_list, node)
	{
		FL_FILE* openFile = fat_list_entry(node, FL_FILE, list_node);

		// If not the current file
		if (openFile != file)
		{
			// Compare path and name
			if ( (fatfs_compare_names(openFile->path,file->path)) && (fatfs_compare_names(openFile->filename,file->filename)) )
				return 1;
		}
	}

	return 0;
}
//-----------------------------------------------------------------------------
// _free_file: Free open file handle
//-----------------------------------------------------------------------------
static void _free_file(FL_FILE* file)
{
	// Remove from open list
	fat_list_remove(&_open_file_list, &file->list_node);

	// Add to free list
	fat_list_insert_last(&_free_file_list, &file->list_node);
}

//-----------------------------------------------------------------------------
//								Low Level
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// _open_directory: Cycle through path string to find the start cluster
// address of the highest subdir.
//-----------------------------------------------------------------------------
static int _open_directory(char *path, uint32 *pathCluster)
{
	int levels;
	int sublevel;
	char currentfolder[FATFS_MAX_LONG_FILENAME];
	struct fat_dir_entry sfEntry;
	uint32 startcluster;

	// Set starting cluster to root cluster
	startcluster = fatfs_get_root_cluster(&_fs);

	// Find number of levels
	levels = fatfs_total_path_levels(path);

	// Cycle through each level and get the start sector
	for (sublevel=0;sublevel<(levels+1);sublevel++)
	{
		if (fatfs_get_substring(path, sublevel, currentfolder, sizeof(currentfolder)) == -1)
			return 0;

		// Find clusteraddress for folder (currentfolder)
		if (fatfs_get_file_entry(&_fs, startcluster, currentfolder,&sfEntry))
		{
			// Check entry is folder
			if (fatfs_entry_is_dir(&sfEntry))
				startcluster = ((FAT_HTONS((uint32)sfEntry.FstClusHI))<<16) + FAT_HTONS(sfEntry.FstClusLO);
			else
				return 0;
		}
		else
			return 0;
	}

	*pathCluster = startcluster;
	return 1;
}
//-----------------------------------------------------------------------------
// _create_directory: Cycle through path string and create the end directory
//-----------------------------------------------------------------------------
#if FATFS_INC_WRITE_SUPPORT
static int _create_directory(char *path)
{
	FL_FILE* file;
	struct fat_dir_entry sfEntry;
	char shortFilename[FAT_SFN_SIZE_FULL];
	int tailNum = 0;
	int i;

	// Allocate a new file handle
	file = _allocate_file();
	if (!file)
		return 0;

	// Clear filename
	memset(file->path, '\0', sizeof(file->path));
	memset(file->filename, '\0', sizeof(file->filename));

	// Split full path into filename and directory path
	if (fatfs_split_path((char*)path, file->path, sizeof(file->path), file->filename, sizeof(file->filename)) == -1)
	{
		_free_file(file);
		return 0;
	}

	// Check if file already open
	if (_check_file_open(file))
	{
		_free_file(file);
		return 0;
	}

	// If file is in the root dir
	if (file->path[0] == 0)
		file->parentcluster = fatfs_get_root_cluster(&_fs);
	else
	{
		// Find parent directory start cluster
		if (!_open_directory(file->path, &file->parentcluster))
		{
			_free_file(file);
			return 0;
		}
	}

	// Check if same filename exists in directory
	if (fatfs_get_file_entry(&_fs, file->parentcluster, file->filename,&sfEntry) == 1)
	{
		_free_file(file);
		return 0;
	}

	file->startcluster = 0;

	// Create the file space for the folder (at least one clusters worth!)
	if (!fatfs_allocate_free_space(&_fs, 1, &file->startcluster, 1))
	{
		_free_file(file);
		return 0;
	}

	// Erase new directory cluster
	memset(file->file_data_sector, 0x00, FAT_SECTOR_SIZE);
	for (i=0;i<_fs.sectors_per_cluster;i++)
	{
		if (!fatfs_write_sector(&_fs, file->startcluster, i, file->file_data_sector))
		{
			_free_file(file);
			return 0;
		}
	}

#if FATFS_INC_LFN_SUPPORT

	// Generate a short filename & tail
	tailNum = 0;
	do
	{
		// Create a standard short filename (without tail)
		fatfs_lfn_create_sfn(shortFilename, file->filename);

        // If second hit or more, generate a ~n tail
		if (tailNum != 0)
			fatfs_lfn_generate_tail((char*)file->shortfilename, shortFilename, tailNum);
		// Try with no tail if first entry
		else
			memcpy(file->shortfilename, shortFilename, FAT_SFN_SIZE_FULL);

		// Check if entry exists already or not
		if (fatfs_sfn_exists(&_fs, file->parentcluster, (char*)file->shortfilename) == 0)
			break;

		tailNum++;
	}
	while (tailNum < 9999);

	// We reached the max number of duplicate short file names (unlikely!)
	if (tailNum == 9999)
	{
		// Delete allocated space
		fatfs_free_cluster_chain(&_fs, file->startcluster);

		_free_file(file);
		return 0;
	}
#else
	// Create a standard short filename (without tail)
	if (!fatfs_lfn_create_sfn(shortFilename, file->filename))
	{
		// Delete allocated space
		fatfs_free_cluster_chain(&_fs, file->startcluster);

		_free_file(file);
		return 0;
	}

	// Copy to SFN space
	memcpy(file->shortfilename, shortFilename, FAT_SFN_SIZE_FULL);

	// Check if entry exists already
	if (fatfs_sfn_exists(&_fs, file->parentcluster, (char*)file->shortfilename))
	{
		// Delete allocated space
		fatfs_free_cluster_chain(&_fs, file->startcluster);

		_free_file(file);
		return 0;
	}
#endif

	// Add file to disk
	if (!fatfs_add_file_entry(&_fs, file->parentcluster, (char*)file->filename, (char*)file->shortfilename, file->startcluster, 0, 1))
	{
		// Delete allocated space
		fatfs_free_cluster_chain(&_fs, file->startcluster);

		_free_file(file);
		return 0;
	}

	// General
	file->filelength = 0;
	file->bytenum = 0;
	file->file_data_address = 0xFFFFFFFF;
	file->file_data_dirty = 0;
	file->filelength_changed = 0;

	// Quick lookup for next link in the chain
	file->last_fat_lookup.ClusterIdx = 0xFFFFFFFF;
	file->last_fat_lookup.CurrentCluster = 0xFFFFFFFF;

	fatfs_fat_purge(&_fs);

	_free_file(file);
	return 1;
}
#endif
//-----------------------------------------------------------------------------
// _open_file: Open a file for reading
//-----------------------------------------------------------------------------
static FL_FILE* _open_file(const char *path)
{
	FL_FILE* file;
	struct fat_dir_entry sfEntry;

	// Allocate a new file handle
	file = _allocate_file();
	if (!file)
		return NULL;

	// Clear filename
	memset(file->path, '\0', sizeof(file->path));
	memset(file->filename, '\0', sizeof(file->filename));

	// Split full path into filename and directory path
	if (fatfs_split_path((char*)path, file->path, sizeof(file->path), file->filename, sizeof(file->filename)) == -1)
	{
		_free_file(file);
		return NULL;
	}

	// Check if file already open
	if (_check_file_open(file))
	{
		_free_file(file);
		return NULL;
	}

	// If file is in the root dir
	if (file->path[0]==0)
		file->parentcluster = fatfs_get_root_cluster(&_fs);
	else
	{
		// Find parent directory start cluster
		if (!_open_directory(file->path, &file->parentcluster))
		{
			_free_file(file);
			return NULL;
		}
	}

	// Using dir cluster address search for filename
	if (fatfs_get_file_entry(&_fs, file->parentcluster, file->filename,&sfEntry))
		// Make sure entry is file not dir!
		if (fatfs_entry_is_file(&sfEntry))
		{
			// Initialise file details
			memcpy(file->shortfilename, sfEntry.Name, FAT_SFN_SIZE_FULL);
			file->filelength = FAT_HTONL(sfEntry.FileSize);
			file->bytenum = 0;
			file->startcluster = ((FAT_HTONS((uint32)sfEntry.FstClusHI))<<16) + FAT_HTONS(sfEntry.FstClusLO);
			file->file_data_address = 0xFFFFFFFF;
			file->file_data_dirty = 0;
			file->filelength_changed = 0;

			// Quick lookup for next link in the chain
			file->last_fat_lookup.ClusterIdx = 0xFFFFFFFF;
			file->last_fat_lookup.CurrentCluster = 0xFFFFFFFF;

			fatfs_cache_init(&_fs, file);

			fatfs_fat_purge(&_fs);

			return file;
		}

	_free_file(file);
	return NULL;
}
//-----------------------------------------------------------------------------
// _create_file: Create a new file
//-----------------------------------------------------------------------------
#if FATFS_INC_WRITE_SUPPORT
static FL_FILE* _create_file(const char *filename)
{
	FL_FILE* file;
	struct fat_dir_entry sfEntry;
	char shortFilename[FAT_SFN_SIZE_FULL];
	int tailNum = 0;

	// No write access?
	if (!_fs.disk_io.write_media)
		return NULL;

	// Allocate a new file handle
	file = _allocate_file();
	if (!file)
		return NULL;

	// Clear filename
	memset(file->path, '\0', sizeof(file->path));
	memset(file->filename, '\0', sizeof(file->filename));

	// Split full path into filename and directory path
	if (fatfs_split_path((char*)filename, file->path, sizeof(file->path), file->filename, sizeof(file->filename)) == -1)
	{
		_free_file(file);
		return NULL;
	}

	// Check if file already open
	if (_check_file_open(file))
	{
		_free_file(file);
		return NULL;
	}

	// If file is in the root dir
	if (file->path[0] == 0)
		file->parentcluster = fatfs_get_root_cluster(&_fs);
	else
	{
		// Find parent directory start cluster
		if (!_open_directory(file->path, &file->parentcluster))
		{
			_free_file(file);
			return NULL;
		}
	}

	// Check if same filename exists in directory
	if (fatfs_get_file_entry(&_fs, file->parentcluster, file->filename,&sfEntry) == 1)
	{
		_free_file(file);
		return NULL;
	}

	file->startcluster = 0;

	// Create the file space for the file (at least one clusters worth!)
	if (!fatfs_allocate_free_space(&_fs, 1, &file->startcluster, 1))
	{
		_free_file(file);
		return NULL;
	}

#if FATFS_INC_LFN_SUPPORT
	// Generate a short filename & tail
	tailNum = 0;
	do
	{
		// Create a standard short filename (without tail)
		fatfs_lfn_create_sfn(shortFilename, file->filename);

        // If second hit or more, generate a ~n tail
		if (tailNum != 0)
			fatfs_lfn_generate_tail((char*)file->shortfilename, shortFilename, tailNum);
		// Try with no tail if first entry
		else
			memcpy(file->shortfilename, shortFilename, FAT_SFN_SIZE_FULL);

		// Check if entry exists already or not
		if (fatfs_sfn_exists(&_fs, file->parentcluster, (char*)file->shortfilename) == 0)
			break;

		tailNum++;
	}
	while (tailNum < 9999);

	// We reached the max number of duplicate short file names (unlikely!)
	if (tailNum == 9999)
	{
		// Delete allocated space
		fatfs_free_cluster_chain(&_fs, file->startcluster);

		_free_file(file);
		return NULL;
	}
#else
	// Create a standard short filename (without tail)
	if (!fatfs_lfn_create_sfn(shortFilename, file->filename))
	{
		// Delete allocated space
		fatfs_free_cluster_chain(&_fs, file->startcluster);

		_free_file(file);
		return NULL;
	}

	// Copy to SFN space
	memcpy(file->shortfilename, shortFilename, FAT_SFN_SIZE_FULL);

	// Check if entry exists already
	if (fatfs_sfn_exists(&_fs, file->parentcluster, (char*)file->shortfilename))
	{
		// Delete allocated space
		fatfs_free_cluster_chain(&_fs, file->startcluster);

		_free_file(file);
		return NULL;
	}
#endif

	// Add file to disk
	if (!fatfs_add_file_entry(&_fs, file->parentcluster, (char*)file->filename, (char*)file->shortfilename, file->startcluster, 0, 0))
	{
		// Delete allocated space
		fatfs_free_cluster_chain(&_fs, file->startcluster);

		_free_file(file);
		return NULL;
	}

	// General
	file->filelength = 0;
	file->bytenum = 0;
	file->file_data_address = 0xFFFFFFFF;
	file->file_data_dirty = 0;
	file->filelength_changed = 0;

	// Quick lookup for next link in the chain
	file->last_fat_lookup.ClusterIdx = 0xFFFFFFFF;
	file->last_fat_lookup.CurrentCluster = 0xFFFFFFFF;

	fatfs_cache_init(&_fs, file);

	fatfs_fat_purge(&_fs);

	return file;
}
#endif
//-----------------------------------------------------------------------------
// _read_sectors: Read sector(s) from disk to file
//-----------------------------------------------------------------------------
static uint32 _read_sectors(FL_FILE* file, uint32 offset, uint8 *buffer, uint32 count)
{
	uint32 Sector = 0;
	uint32 ClusterIdx = 0;
	uint32 Cluster = 0;
	uint32 i;
	uint32 lba;

	// Find cluster index within file & sector with cluster
	ClusterIdx = offset / _fs.sectors_per_cluster;
	Sector = offset - (ClusterIdx * _fs.sectors_per_cluster);

	// Limit number of sectors read to the number remaining in this cluster
	if ((Sector + count) > _fs.sectors_per_cluster)
		count = _fs.sectors_per_cluster - Sector;

	// Quick lookup for next link in the chain
	if (ClusterIdx == file->last_fat_lookup.ClusterIdx)
		Cluster = file->last_fat_lookup.CurrentCluster;
	// Else walk the chain
	else
	{
		// Starting from last recorded cluster?
		if (ClusterIdx && ClusterIdx == file->last_fat_lookup.ClusterIdx + 1)
		{
			i = file->last_fat_lookup.ClusterIdx;
			Cluster = file->last_fat_lookup.CurrentCluster;
		}
		// Start searching from the beginning..
		else
		{
			// Set start of cluster chain to initial value
			i = 0;
			Cluster = file->startcluster;
		}

		// Follow chain to find cluster to read
		for ( ;i<ClusterIdx; i++)
		{
			uint32 nextCluster;

			// Does the entry exist in the cache?
			if (!fatfs_cache_get_next_cluster(&_fs, file, i, &nextCluster))
			{
				// Scan file linked list to find next entry
				nextCluster = fatfs_find_next_cluster(&_fs, Cluster);

				// Push entry into cache
				fatfs_cache_set_next_cluster(&_fs, file, i, nextCluster);
			}

			Cluster = nextCluster;
		}

		// Record current cluster lookup details (if valid)
		if (Cluster != FAT32_LAST_CLUSTER)
		{
			file->last_fat_lookup.CurrentCluster = Cluster;
			file->last_fat_lookup.ClusterIdx = ClusterIdx;
		}
	}

	// If end of cluster chain then return false
	if (Cluster == FAT32_LAST_CLUSTER)
		return 0;

	// Calculate sector address
	lba = fatfs_lba_of_cluster(&_fs, Cluster) + Sector;

	// Read sector of file
	if (fatfs_sector_read(&_fs, lba, buffer, count))
		return count;
	else
		return 0;
}

//-----------------------------------------------------------------------------
//								External API
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// fl_init: Initialise library
//-----------------------------------------------------------------------------
void fl_init(void)
{
	int i;

	fat_list_init(&_free_file_list);
	fat_list_init(&_open_file_list);

	// Add all file objects to free list
	for (i=0;i<FATFS_MAX_OPEN_FILES;i++)
		fat_list_insert_last(&_free_file_list, &_files[i].list_node);

	_filelib_init = 1;
}
//-----------------------------------------------------------------------------
// fl_attach_locks:
//-----------------------------------------------------------------------------
void fl_attach_locks(struct fatfs *fs, void (*lock)(void), void (*unlock)(void))
{
	fs->fl_lock = lock;
	fs->fl_unlock = unlock;
}
//-----------------------------------------------------------------------------
// fl_attach_media:
//-----------------------------------------------------------------------------
int fl_attach_media(fn_diskio_read rd, fn_diskio_write wr)
{
	int res;

	// If first call to library, initialise
	CHECK_FL_INIT();

	_fs.disk_io.read_media = rd;
	_fs.disk_io.write_media = wr;

	// Initialise FAT parameters
	if ((res = fatfs_init(&_fs)) != FAT_INIT_OK)
	{
		FAT_PRINTF(("FAT_FS: Error could not load FAT details (%d)!\r\n", res));
		return res;
	}

	_filelib_valid = 1;
	return FAT_INIT_OK;
}
//-----------------------------------------------------------------------------
// fl_shutdown: Call before shutting down system
//-----------------------------------------------------------------------------
void fl_shutdown(void)
{
	// If first call to library, initialise
	CHECK_FL_INIT();

	FL_LOCK(&_fs);
	fatfs_fat_purge(&_fs);
	FL_UNLOCK(&_fs);
}
//-----------------------------------------------------------------------------
// fopen: Open or Create a file for reading or writing
//-----------------------------------------------------------------------------
void* fl_fopen(const char *path, const char *mode)
{
	int i;
	FL_FILE* file;
	uint8 flags = 0;

	// If first call to library, initialise
	CHECK_FL_INIT();

	if (!_filelib_valid)
		return NULL;

	if (!path || !mode)
		return NULL;

	// Supported Modes:
	// "r" Open a file for reading.
	//		The file must exist.
	// "w" Create an empty file for writing.
	//		If a file with the same name already exists its content is erased and the file is treated as a new empty file.
	// "a" Append to a file.
	//		Writing operations append data at the end of the file.
	//		The file is created if it does not exist.
	// "r+" Open a file for update both reading and writing.
	//		The file must exist.
	// "w+" Create an empty file for both reading and writing.
	//		If a file with the same name already exists its content is erased and the file is treated as a new empty file.
	// "a+" Open a file for reading and appending.
	//		All writing operations are performed at the end of the file, protecting the previous content to be overwritten.
	//		You can reposition (fseek, rewind) the internal pointer to anywhere in the file for reading, but writing operations
	//		will move it back to the end of file.
	//		The file is created if it does not exist.

	for (i=0;i<(int)strlen(mode);i++)
	{
		switch (tolower(mode[i]))
		{
		case 'r':
			flags |= FILE_READ;
			break;
		case 'w':
			flags |= FILE_WRITE;
			flags |= FILE_ERASE;
			flags |= FILE_CREATE;
			break;
		case 'a':
			flags |= FILE_WRITE;
			flags |= FILE_APPEND;
			flags |= FILE_CREATE;
			break;
		case '+':
			if (flags & FILE_READ)
				flags |= FILE_WRITE;
			else if (flags & FILE_WRITE)
			{
				flags |= FILE_READ;
				flags |= FILE_ERASE;
				flags |= FILE_CREATE;
			}
			else if (flags & FILE_APPEND)
			{
				flags |= FILE_READ;
				flags |= FILE_WRITE;
				flags |= FILE_APPEND;
				flags |= FILE_CREATE;
			}
			break;
		case 'b':
			flags |= FILE_BINARY;
			break;
		}
	}

	file = NULL;

#if FATFS_INC_WRITE_SUPPORT == 0
	// No write support!
	flags &= ~(FILE_CREATE | FILE_WRITE | FILE_APPEND);
#endif

	// No write access - remove write/modify flags
	if (!_fs.disk_io.write_media)
		flags &= ~(FILE_CREATE | FILE_WRITE | FILE_APPEND);

	FL_LOCK(&_fs);

	// Read
	if (flags & FILE_READ)
		file = _open_file(path);

	// Create New
#if FATFS_INC_WRITE_SUPPORT
	if (!file && (flags & FILE_CREATE))
		file = _create_file(path);
#endif

	// Write Existing (and not open due to read or create)
	if (!(flags & FILE_READ))
		if ((flags & FILE_CREATE) && !file)
			if (flags & (FILE_WRITE | FILE_APPEND))
				file = _open_file(path);

	if (file)
		file->flags = flags;

	FL_UNLOCK(&_fs);
	return file;
}
//-----------------------------------------------------------------------------
// _write_sectors: Write sector(s) to disk
//-----------------------------------------------------------------------------
#if FATFS_INC_WRITE_SUPPORT
static uint32 _write_sectors(FL_FILE* file, uint32 offset, uint8 *buf, uint32 count)
{
	uint32 SectorNumber = 0;
	uint32 ClusterIdx = 0;
	uint32 Cluster = 0;
	uint32 LastCluster = FAT32_LAST_CLUSTER;
	uint32 i;
	uint32 lba;
	uint32 TotalWriteCount = count;

	// Find values for Cluster index & sector within cluster
	ClusterIdx = offset / _fs.sectors_per_cluster;
	SectorNumber = offset - (ClusterIdx * _fs.sectors_per_cluster);

	// Limit number of sectors written to the number remaining in this cluster
	if ((SectorNumber + count) > _fs.sectors_per_cluster)
		count = _fs.sectors_per_cluster - SectorNumber;

	// Quick lookup for next link in the chain
	if (ClusterIdx == file->last_fat_lookup.ClusterIdx)
		Cluster = file->last_fat_lookup.CurrentCluster;
	// Else walk the chain
	else
	{
		// Starting from last recorded cluster?
		if (ClusterIdx && ClusterIdx == file->last_fat_lookup.ClusterIdx + 1)
		{
			i = file->last_fat_lookup.ClusterIdx;
			Cluster = file->last_fat_lookup.CurrentCluster;
		}
		// Start searching from the beginning..
		else
		{
			// Set start of cluster chain to initial value
			i = 0;
			Cluster = file->startcluster;
		}

		// Follow chain to find cluster to read
		for ( ;i<ClusterIdx; i++)
		{
			uint32 nextCluster;

			// Does the entry exist in the cache?
			if (!fatfs_cache_get_next_cluster(&_fs, file, i, &nextCluster))
			{
				// Scan file linked list to find next entry
				nextCluster = fatfs_find_next_cluster(&_fs, Cluster);

				// Push entry into cache
				fatfs_cache_set_next_cluster(&_fs, file, i, nextCluster);
			}

			LastCluster = Cluster;
			Cluster = nextCluster;

			// Dont keep following a dead end
			if (Cluster == FAT32_LAST_CLUSTER)
				break;
		}

		// If we have reached the end of the chain, allocate more!
		if (Cluster == FAT32_LAST_CLUSTER)
		{
			// Add some more cluster(s) to the last good cluster chain
			if (!fatfs_add_free_space(&_fs, &LastCluster,  (TotalWriteCount + _fs.sectors_per_cluster -1) / _fs.sectors_per_cluster))
				return 0;

			Cluster = LastCluster;
		}

		// Record current cluster lookup details
		file->last_fat_lookup.CurrentCluster = Cluster;
		file->last_fat_lookup.ClusterIdx = ClusterIdx;
	}

	// Calculate write address
	lba = fatfs_lba_of_cluster(&_fs, Cluster) + SectorNumber;

	if (fatfs_sector_write(&_fs, lba, buf, count))
		return count;
	else
		return 0;
}
#endif
//-----------------------------------------------------------------------------
// fl_fflush: Flush un-written data to the file
//-----------------------------------------------------------------------------
int fl_fflush(void *f)
{
#if FATFS_INC_WRITE_SUPPORT
	FL_FILE *file = (FL_FILE *)f;

	// If first call to library, initialise
	CHECK_FL_INIT();

	if (file)
	{
		FL_LOCK(&_fs);

		// If some write data still in buffer
		if (file->file_data_dirty)
		{
			// Write back current sector before loading next
			if (_write_sectors(file, file->file_data_address, file->file_data_sector, 1))
				file->file_data_dirty = 0;
		}

		FL_UNLOCK(&_fs);
	}
#endif
	return 0;
}
//-----------------------------------------------------------------------------
// fl_fclose: Close an open file
//-----------------------------------------------------------------------------
void fl_fclose(void *f)
{
	FL_FILE *file = (FL_FILE *)f;

	// If first call to library, initialise
	CHECK_FL_INIT();

	if (file)
	{
		FL_LOCK(&_fs);

		// Flush un-written data to file
		fl_fflush(f);

		// File size changed?
		if (file->filelength_changed)
		{
#if FATFS_INC_WRITE_SUPPORT
			// Update filesize in directory
			fatfs_update_file_length(&_fs, file->parentcluster, (char*)file->shortfilename, file->filelength);
#endif
			file->filelength_changed = 0;
		}

		file->bytenum = 0;
		file->filelength = 0;
		file->startcluster = 0;
		file->file_data_address = 0xFFFFFFFF;
		file->file_data_dirty = 0;
		file->filelength_changed = 0;

		// Free file handle
		_free_file(file);

		fatfs_fat_purge(&_fs);

		FL_UNLOCK(&_fs);
	}
}
//-----------------------------------------------------------------------------
// fl_fgetc: Get a character in the stream
//-----------------------------------------------------------------------------
int fl_fgetc(void *f)
{
	int res;
	uint8 data = 0;

	res = fl_fread(&data, 1, 1, f);
	if (res == 1)
		return (int)data;
	else
		return res;
}
//-----------------------------------------------------------------------------
// fl_fread: Read a block of data from the file
//-----------------------------------------------------------------------------
int fl_fread(void * buffer, int size, int length, void *f )
{
	uint32 sector;
	uint32 offset;
	int copyCount;
	int count = size * length;
	int bytesRead = 0;

	FL_FILE *file = (FL_FILE *)f;

	// If first call to library, initialise
	CHECK_FL_INIT();

	if (buffer==NULL || file==NULL)
		return -1;

	// No read permissions
	if (!(file->flags & FILE_READ))
		return -1;

	// Nothing to be done
	if (!count)
		return 0;

	// Check if read starts past end of file
	if (file->bytenum >= file->filelength)
		return -1;

	// Limit to file size
	if ( (file->bytenum + count) > file->filelength )
		count = file->filelength - file->bytenum;

	// Calculate start sector
	sector = file->bytenum / FAT_SECTOR_SIZE;

	// Offset to start copying data from first sector
	offset = file->bytenum % FAT_SECTOR_SIZE;

	while (bytesRead < count)
	{
		// Read whole sector, read from media directly into target buffer
		if ((offset == 0) && ((count - bytesRead) >= FAT_SECTOR_SIZE))
		{
			// Read as many sectors as possible into target buffer
			uint32 sectorsRead = _read_sectors(file, sector, (uint8*)((uint8*)buffer + bytesRead), (count - bytesRead) / FAT_SECTOR_SIZE);
			if (sectorsRead)
			{
				// We have upto one sector to copy
				copyCount = FAT_SECTOR_SIZE * sectorsRead;

				// Move onto next sector and reset copy offset
				sector+= sectorsRead;
				offset = 0;
			}
			else
				break;
		}
		else
		{
			// Do we need to re-read the sector?
			if (file->file_data_address != sector)
			{
				// Flush un-written data to file
				if (file->file_data_dirty)
					fl_fflush(file);

				// Get LBA of sector offset within file
				if (!_read_sectors(file, sector, file->file_data_sector, 1))
					// Read failed - out of range (probably)
					break;

				file->file_data_address = sector;
				file->file_data_dirty = 0;
			}

			// We have upto one sector to copy
			copyCount = FAT_SECTOR_SIZE - offset;

			// Only require some of this sector?
			if (copyCount > (count - bytesRead))
				copyCount = (count - bytesRead);

			// Copy to application buffer
			memcpy( (uint8*)((uint8*)buffer + bytesRead), (uint8*)(file->file_data_sector + offset), copyCount);

			// Move onto next sector and reset copy offset
			sector++;
			offset = 0;
		}

		// Increase total read count
		bytesRead += copyCount;

		// Increment file pointer
		file->bytenum += copyCount;
	}

	return bytesRead;
}
//-----------------------------------------------------------------------------
// fl_fseek: Seek to a specific place in the file
//-----------------------------------------------------------------------------
int fl_fseek( void *f, long offset, int origin )
{
	FL_FILE *file = (FL_FILE *)f;
	int res = -1;

	// If first call to library, initialise
	CHECK_FL_INIT();

	if (!file)
		return -1;

	if (origin == SEEK_END && offset != 0)
		return -1;

	FL_LOCK(&_fs);

	// Invalidate file buffer
	file->file_data_address = 0xFFFFFFFF;
	file->file_data_dirty = 0;

	if (origin == SEEK_SET)
	{
		file->bytenum = (uint32)offset;

		if (file->bytenum > file->filelength)
			file->bytenum = file->filelength;

		res = 0;
	}
	else if (origin == SEEK_CUR)
	{
		// Positive shift
		if (offset >= 0)
		{
			file->bytenum += offset;

			if (file->bytenum > file->filelength)
				file->bytenum = file->filelength;
		}
		// Negative shift
		else
		{
			// Make shift positive
			offset = -offset;

			// Limit to negative shift to start of file
			if ((uint32)offset > file->bytenum)
				file->bytenum = 0;
			else
				file->bytenum-= offset;
		}

		res = 0;
	}
	else if (origin == SEEK_END)
	{
		file->bytenum = file->filelength;
		res = 0;
	}
	else
		res = -1;

	FL_UNLOCK(&_fs);

	return res;
}
//-----------------------------------------------------------------------------
// fl_fgetpos: Get the current file position
//-----------------------------------------------------------------------------
int fl_fgetpos(void *f , uint32 * position)
{
	FL_FILE *file = (FL_FILE *)f;

	if (!file)
		return -1;

	FL_LOCK(&_fs);

	// Get position
	*position = file->bytenum;

	FL_UNLOCK(&_fs);

	return 0;
}
//-----------------------------------------------------------------------------
// fl_ftell: Get the current file position
//-----------------------------------------------------------------------------
long fl_ftell(void *f)
{
	uint32 pos = 0;

	fl_fgetpos(f, &pos);

	return (long)pos;
}
//-----------------------------------------------------------------------------
// fl_feof: Is the file pointer at the end of the stream?
//-----------------------------------------------------------------------------
int fl_feof(void *f)
{
	FL_FILE *file = (FL_FILE *)f;
	int res;

	if (!file)
		return -1;

	FL_LOCK(&_fs);

	if (file->bytenum == file->filelength)
		res = EOF;
	else
		res = 0;

	FL_UNLOCK(&_fs);

	return res;
}
//-----------------------------------------------------------------------------
// fl_fputc: Write a character to the stream
//-----------------------------------------------------------------------------
#if FATFS_INC_WRITE_SUPPORT
int fl_fputc(int c, void *f)
{
	uint8 data = (uint8)c;
	int res;

	res = fl_fwrite(&data, 1, 1, f);
	if (res == 1)
		return c;
	else
		return res;
}
#endif
//-----------------------------------------------------------------------------
// fl_fwrite: Write a block of data to the stream
//-----------------------------------------------------------------------------
#if FATFS_INC_WRITE_SUPPORT
int fl_fwrite(const void * data, int size, int count, void *f )
{
	FL_FILE *file = (FL_FILE *)f;
	uint32 sector;
	uint32 offset;
	uint32 length = (size*count);
	uint8 *buffer = (uint8 *)data;
	uint32 bytesWritten = 0;
	uint32 copyCount;

	// If first call to library, initialise
	CHECK_FL_INIT();

	if (!file)
		return -1;

	FL_LOCK(&_fs);

	// No write permissions
	if (!(file->flags & FILE_WRITE))
	{
		FL_UNLOCK(&_fs);
		return -1;
	}

	// Append writes to end of file
	if (file->flags & FILE_APPEND)
		file->bytenum = file->filelength;
	// Else write to current position

	// Calculate start sector
	sector = file->bytenum / FAT_SECTOR_SIZE;

	// Offset to start copying data from first sector
	offset = file->bytenum % FAT_SECTOR_SIZE;

	while (bytesWritten < length)
	{
		// Whole sector or more to be written?
		if ((offset == 0) && ((length - bytesWritten) >= FAT_SECTOR_SIZE))
		{
			uint32 sectorsWrote;

			// Buffered sector, flush back to disk
			if (file->file_data_address != 0xFFFFFFFF)
			{
				// Flush un-written data to file
				if (file->file_data_dirty)
					fl_fflush(file);

				file->file_data_address = 0xFFFFFFFF;
				file->file_data_dirty = 0;
			}

			// Write as many sectors as possible
			sectorsWrote = _write_sectors(file, sector, (uint8*)(buffer + bytesWritten), (length - bytesWritten) / FAT_SECTOR_SIZE);
			copyCount = FAT_SECTOR_SIZE * sectorsWrote;

			// Increase total read count
			bytesWritten += copyCount;

			// Increment file pointer
			file->bytenum += copyCount;

			// Move onto next sector and reset copy offset
			sector+= sectorsWrote;
			offset = 0;

			if (!sectorsWrote)
				break;
		}
		else
		{
			// We have upto one sector to copy
			copyCount = FAT_SECTOR_SIZE - offset;

			// Only require some of this sector?
			if (copyCount > (length - bytesWritten))
				copyCount = (length - bytesWritten);

			// Do we need to read a new sector?
			if (file->file_data_address != sector)
			{
				// Flush un-written data to file
				if (file->file_data_dirty)
					fl_fflush(file);

				// If we plan to overwrite the whole sector, we don't need to read it first!
				if (copyCount != FAT_SECTOR_SIZE)
				{
					// NOTE: This does not have succeed; if last sector of file
					// reached, no valid data will be read in, but write will
					// allocate some more space for new data.

					// Get LBA of sector offset within file
					if (!_read_sectors(file, sector, file->file_data_sector, 1))
						memset(file->file_data_sector, 0x00, FAT_SECTOR_SIZE);
				}

				file->file_data_address = sector;
				file->file_data_dirty = 0;
			}

			// Copy from application buffer into sector buffer
			memcpy((uint8*)(file->file_data_sector + offset), (uint8*)(buffer + bytesWritten), copyCount);

			// Mark buffer as dirty
			file->file_data_dirty = 1;

			// Increase total read count
			bytesWritten += copyCount;

			// Increment file pointer
			file->bytenum += copyCount;

			// Move onto next sector and reset copy offset
			sector++;
			offset = 0;
		}
	}

	// Write increased extent of the file?
	if (file->bytenum > file->filelength)
	{
		// Increase file size to new point
		file->filelength = file->bytenum;

		// We are changing the file length and this
		// will need to be writen back at some point
		file->filelength_changed = 1;
	}

	FL_UNLOCK(&_fs);

	return (size*count);
}
#endif
//-----------------------------------------------------------------------------
// fl_fputs: Write a character string to the stream
//-----------------------------------------------------------------------------
#if FATFS_INC_WRITE_SUPPORT
int fl_fputs(const char * str, void *f)
{
	int len = (int)strlen(str);
	int res = fl_fwrite(str, 1, len, f);

	if (res == len)
		return len;
	else
		return res;
}
#endif
//-----------------------------------------------------------------------------
// fl_remove: Remove a file from the filesystem
//-----------------------------------------------------------------------------
#if FATFS_INC_WRITE_SUPPORT
int fl_remove( const char * filename )
{
	FL_FILE* file;
	int res = -1;

	FL_LOCK(&_fs);

	// Use read_file as this will check if the file is already open!
	file = fl_fopen((char*)filename, "r");
	if (file)
	{
		// Delete allocated space
		if (fatfs_free_cluster_chain(&_fs, file->startcluster))
		{
			// Remove directory entries
			if (fatfs_mark_file_deleted(&_fs, file->parentcluster, (char*)file->shortfilename))
			{
				// Close the file handle (this should not write anything to the file
				// as we have not changed the file since opening it!)
				fl_fclose(file);

				res = 0;
			}
		}
	}

	FL_UNLOCK(&_fs);

	return res;
}
#endif
//-----------------------------------------------------------------------------
// fl_createdirectory: Create a directory based on a path
//-----------------------------------------------------------------------------
#if FATFS_INC_WRITE_SUPPORT
int fl_createdirectory(const char *path)
{
	int res;

	// If first call to library, initialise
	CHECK_FL_INIT();

	FL_LOCK(&_fs);
	res =_create_directory((char*)path);
	FL_UNLOCK(&_fs);

	return res;
}
#endif
//-----------------------------------------------------------------------------
// fl_listdirectory: List a directory based on a path
//-----------------------------------------------------------------------------
#if FATFS_DIR_LIST_SUPPORT
void fl_listdirectory(const char *path)
{
	FL_DIR dirstat;
	int filenumber = 0;

	// If first call to library, initialise
	CHECK_FL_INIT();

	FL_LOCK(&_fs);

	FAT_PRINTF(("\r\nNo.             Filename\r\n"));

	if (fl_opendir(path, &dirstat))
	{
		struct fs_dir_ent dirent;

		while (fl_readdir(&dirstat, &dirent) == 0)
		{
			if (dirent.is_dir)
			{
				FAT_PRINTF(("%d - %s <DIR> (0x%x)\r\n",++filenumber, dirent.filename, dirent.cluster));
			}
			else
			{
				FAT_PRINTF(("%d - %s [%d bytes] (0x%x)\r\n",++filenumber, dirent.filename, dirent.size, dirent.cluster));
			}
		}

		fl_closedir(&dirstat);
		FAT_PRINTF(("\r\nLooking for files Compleate.Files:%d",filenumber));
	}

	FL_UNLOCK(&_fs);
}
#endif
//-----------------------------------------------------------------------------
// fl_opendir: Opens a directory for listing
//-----------------------------------------------------------------------------
#if FATFS_DIR_LIST_SUPPORT
FL_DIR* fl_opendir(const char* path, FL_DIR *dir)
{
	int levels;
	int res = 1;
	uint32 cluster = FAT32_INVALID_CLUSTER;

	// If first call to library, initialise
	CHECK_FL_INIT();

	FL_LOCK(&_fs);

	levels = fatfs_total_path_levels((char*)path) + 1;

	// If path is in the root dir
	if (levels == 0)
		cluster = fatfs_get_root_cluster(&_fs);
	// Find parent directory start cluster
	else
		res = _open_directory((char*)path, &cluster);

	if (res)
		fatfs_list_directory_start(&_fs, dir, cluster);

	FL_UNLOCK(&_fs);

	return cluster != FAT32_INVALID_CLUSTER ? dir : 0;
}
#endif
//-----------------------------------------------------------------------------
// fl_readdir: Get next item in directory
//-----------------------------------------------------------------------------
#if FATFS_DIR_LIST_SUPPORT
int fl_readdir(FL_DIR *dirls, fl_dirent *entry)
{
	int res = 0;

	// If first call to library, initialise
	CHECK_FL_INIT();

	FL_LOCK(&_fs);

	res = fatfs_list_directory_next(&_fs, dirls, entry);

	FL_UNLOCK(&_fs);

	return res ? 0 : -1;
}
#endif
//-----------------------------------------------------------------------------
// fl_closedir: Close directory after listing
//-----------------------------------------------------------------------------
#if FATFS_DIR_LIST_SUPPORT
int fl_closedir(FL_DIR* dir)
{
	// Not used
	return 0;
}
#endif
//-----------------------------------------------------------------------------
// fl_is_dir: Is this a directory?
//-----------------------------------------------------------------------------
int fl_is_dir(const char *path)
{
	int res = 0;
	FL_DIR dir;

	if (fl_opendir(path, &dir))
	{
		res = 1;
		fl_closedir(&dir);
	}

	return res;
}
//-----------------------------------------------------------------------------
// fl_get_fs:
//-----------------------------------------------------------------------------
#ifdef FATFS_INC_TEST_HOOKS
struct fatfs* fl_get_fs(void)
{
	return &_fs;
}
#endif
